Sluzba byla v katalogu bez pristupovych udaju a s appId 'ai-text', tedy mirila na aplikaci, ktera neexistuje. Konektor nemel co vyplnit a krok nemel co vykonat. - sluzba stoji na api.openai.com/v1 stejne jako OpenAI, overeni GET /models - model se bere z konektoru (ctx.config.model), ne z kazdeho kroku zvlast - skripty ai-text.classify, ai-text.extract, ai-text.generate Proc to neni jedna sluzba s OpenAI: tam se posila volny dotaz a vraci volny text. Tady je uloha pevna a vystup taky - category a confidence, objekt s popsanymi poli, text s poctem slov. Na to jde ve strome navazat podminkou, na volny text ne. Zjisteno u toho a zapsane do 21-realne-sluzby.md: proxy vraci na neznamou aplikaci 200 s prazdnym telem, takze overeni konektoru u sluzby bez verifyPath projde i tam, kde nic nebezi. Zatim neopraveno. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
19 KiB
21 - Realne sluzby a co k nim potreba
Naprogramovano. Tenhle dokument rika, ktera sluzba v katalogu ma za sebou opravdu bezici aplikaci, jake udaje po firme chce a co s ni umime udelat.
Obecny popis vrstev je v 12-sluzby-a-konektory.md, popis skriptu v 11-skripty-konektoru.md.
Zdroj pravdy
Seznam bezicich aplikaci je na https://services.csbot.cz/apps. Kazda ma
/docs se Swaggerem a /openapi.json (u .NET aplikaci /docs/v1/swagger.json)
se strojove citelnym popisem.
Katalog v src/data/services.ts z toho vychazi. Neni to totez: jedna
aplikace muze nest vic sluzeb katalogu a nektere sluzby katalogu zatim zadnou
aplikaci nemaji.
Ktera sluzba stoji na cem
| Sluzba katalogu | Aplikace (appId) |
Overeni (verifyPath) |
|---|---|---|
| iDoklad | idoklad |
/account/agenda |
| RAYNET CRM | raynet |
/company?limit=1 |
| CSOB (PSD2) | csob |
/accounts?size=1 |
| SAP Business One | sap-bo |
/api/system/info |
| PPL CPL | pplcplapi |
/customer |
| Microsoft 365 | microsoft-365-service |
/status |
| Google Workspace | google-service |
/google/drive/files |
| Google Analytics 4 | analytics |
/ga/admin/accountSummaries |
| Search Console | analytics |
/gsc/sites |
| Google Ads | analytics |
/googleads/customers:listAccessibleCustomers |
| Sklik | analytics |
/sklik/limits |
| Meta Ads | meta |
/ads/me/adaccounts |
| Prepis hovoru | audio-transcription |
nema, overi se jen dostupnost |
| SMTP server firmy | prihlaseni na server, nic se neodesila | |
| OpenAI | mimo nas, api.openai.com/v1 |
/models |
| AI zpracovani textu | mimo nas, api.openai.com/v1 |
/models |
Ctyri sluzby na jedne aplikaci. GA4, Search Console, Google Ads a Sklik
bezi v analytics, ale kazda ma jine pristupove udaje a jine ceny za
pristup. Slucovat je do jedne sluzby by znamenalo, ze firma, ktera ma jen
Sklik, musi zaroven vyplnit Google. Proto jsou to ctyri sluzby s jednim appId.
Prepis hovoru nema overeni udaju. Jeho jedine volani je prepis, ktery se uctuje. Overeni proto rekne jen "sluzba odpovida" a nahlas dodá, ze udaje overene nejsou - test, ktery projde i se spatnym klicem, by lhal.
Sluzby, ktere aplikaci zatim nemaji
E-shop, WhatsApp, Facebook Messenger, Instagram, SMS a Slack jsou v katalogu jako popis toho, co chceme umet. Konektor u nich zalozit jde, ale nic za nim neni.
Pozor: overeni u nich projde. Proxy vraci na neznamou aplikaci 200
s prazdnym telem, takze GET /apps/<neco>/health vypada jako zdrava sluzba
i u aplikace, ktera neexistuje:
curl -o /dev/null -w "%{http_code}" https://services.csbot.cz/apps/tohle-neexistuje-xyz/health
# 200
Sluzba bez verifyPath se overuje prave pres /health, takze konektor rekne
"Sluzba odpovida" i tam, kde nic nebezi. Neni to chyba overovani, je to chyba
predpokladu: 2xx od proxy neznamena, ze za ni neco je. Napravit to znamena
vyzadovat, aby /health neco odpovedel, ne jen vratil kod.
Meta Ads je neco jineho nez Facebook Messenger a Instagram: aplikace meta
je nad Marketing API, tedy reklamy, a je jen pro cteni. Zpravy ze stranky
a prime zpravy ta aplikace neumi, proto se na ni ty dve sluzby nenapojily.
Co ktera sluzba chce po firme
Udaje patri konektoru, ne prostredi. Tajne se z API nikdy nevraci.
RAYNET CRM
| Pole | Hlavicka | Kde to vzit |
|---|---|---|
| API klic | X-Api-Key |
RAYNET: Nastaveni, Klic k API |
| E-mail uzivatele | X-Raynet-Email |
prihlasovaci e-mail |
| Nazev instance | X-Instance-Name |
subdomena uctu |
CSOB (PSD2)
Nejvic udaju z celeho katalogu, a je to tak spravne: bankovni rozhrani chce
certifikat i token. X-Access-Token navic casem vyprsi a musi se prepsat,
jinak konektor prestane fungovat, aniz by se cokoliv jineho zmenilo.
Certifikat QWAC se vklada jako PFX zakodovany do Base64.
SAP Business One
X-SAP-B1-BaseUrl je adresa Service Layer u zakaznika a musi byt dostupna
z internetu. X-SAP-B1-Reject-Unauthorized se nastavi na false jen tam, kde
ma Service Layer self-signed certifikat.
PPL CPL
Client ID a Client Secret z vyvojarskeho portalu. X-Environment prepne na
testovaci prostredi, ktere nevytvari skutecne zasilky - hodi se pri
zkousení stromu.
Microsoft 365
Tenant ID, Client ID a Client Secret registrovane aplikace v Entra ID. Prihlasuje se aplikace, ne clovek, takze kazdy krok rika, ktere schranky se tyka.
Google Workspace
Dve cesty, staci jedna:
- JSON klic service accountu (
X-Google-Service-Account-Json) plus opravneni (X-Google-Service-Account-Scopes). Tohle je cesta pro provoz bez cloveka: sluzba si z klice vystavi token sama. - Hotovy access token (
X-Google-Access-Token). Plati asi hodinu, takze na trvaly provoz to neni.
Overeni konektoru cte Disk, takze service account potrebuje aspon scope
https://www.googleapis.com/auth/drive.readonly. Bez nej test skonci chybou,
i kdyz je klic v poradku.
Google Analytics 4, Search Console, Google Ads
Vsechny tri pouzivaji tentyz princip prihlaseni pres Google, jen s jinym prefixem hlavicky. Jeden service account staci na vsechny tri, kdyz se mu v kazde sluzbe udeli pristup. Google Ads navic vzdy potrebuje developer token.
Sklik
Jeden token z API Drak. Vygenerovani noveho tokenu zneplatni ten predchozi, takze se u sdileneho uctu vyplati vedet, kdo ho generoval naposled.
Meta Ads
Token systemoveho uzivatele z Business Manageru. Uzivatelsky token prestane
platit, kdyz clovek odejde z firmy nebo si zmeni heslo, takze se pro
server-to-server nehodi. App secret je povinny tam, kde ma aplikace zapnute
appsecret_proof.
Prepis hovoru
Deepgram i OpenAI klic. Obe sluzby bezi paralelne a treti volani jejich vysledky slucuje, takze bez obou klicu to nefunguje.
OpenAI a AI zpracovani textu
Obe stoji na temze API a obe chteji jen API klic. Zadava se holy, slovo
Bearer dopise portal - viz nize.
Rozdil je v tom, na co se ptaji. OpenAI posila volny dotaz a vraci volny text; hodi se, kdyz clovek vi, co chce napsat. AI zpracovani textu ma hotove ulohy s pevnym vystupem:
| Operace | Vraci |
|---|---|
| Zaradit do kategorie | category, confidence a jestli je ze seznamu |
| Vytahnout udaje | objekt s poli, ktera si popsal uzivatel |
| Vygenerovat text | text plus pocet slov |
Pevny vystup je ten duvod, proc to neni jedna sluzba: na category
a confidence jde ve strome navazat podminkou, na volny text ne.
Model se u teto sluzby bere z konektoru (ctx.config.model), aby se nemusel
vyplnovat u kazdeho kroku znovu.
OpenAI: sluzba, ktera nebezi u nas
Zbytek katalogu jsou nase aplikace za services.csbot.cz/apps. OpenAI je cizi
domena, se kterou nemuzeme hnout, takze se s ni zachazi jinak na trech mistech.
Adresa je u sluzby, ne z appId
Service ma nove nepovinne pole baseUrl s absolutni adresou. Skladat adresu
ze SERVICES_BASE_URL by u ni nedavalo smysl - to je zaklad nasich
aplikaci.
Prepsat ji jde dvema zpusoby:
| Kudy | Pro koho plati | K cemu |
|---|---|---|
OPENAI_BASE_URL |
cela instance | brána, napodobenina pri vyvoji |
| adresa u konektoru | jedna firma | vlastni Azure OpenAI |
Obecne: <SLUZBA>_BASE_URL, kde se z ID sluzby udelaji velka pismena
a pomlcka je podtrzitko (sap-bo je SAP_BO_BASE_URL).
Klic se zadava holy
OpenAI chce Authorization: Bearer <klic>. Kdyby si mel uzivatel slovo
Bearer psat sam, byl by to prvni zdroj chyb, ktery neni videt ani zpetne -
hodnota se z API nevraci, takze preklep v ni uz nikdo nenajde.
Pole udaju proto ma nepovinny prefix a runtime ho doplni az pri sestaveni
hlavicky. Redakce v logu se dela na obojí: na cely retezec i na samotny klic,
protoze cizi sluzby vraci v chybe jednou jedno a jednou druhé.
Co s OpenAI umime
| Operace | Endpoint | K cemu |
|---|---|---|
| Zeptat se modelu | POST /chat/completions |
shrnuti, klasifikace, sepsani odpovedi |
| Nahrat soubor | POST /files |
vrati fileId pro dalsi krok |
| Zeptat se na soubor | POST /responses |
vytezeni faktury, smlouvy, fotky |
| Prepsat zvuk | POST /audio/transcriptions |
jeden pruchod prepisem |
| Nacist seznam modelu | GET /models |
co ucet umi, nic nestoji |
Model je volny text s vychozi hodnotou, ne vyber ze seznamu. Pevny seznam by zestarl pri kazdem vydani noveho modelu a krok stromu by pak odmital hodnotu, kterou ucet umi. Co ucet umi, vrati operace Nacist seznam modelu.
Otazka nad souborem je jiny endpoint nez obycejny dotaz. Soubor jako vstup umi az Responses API; Chat Completions by prijalo jen text, takze by se obsah PDF musel vlepit rucne - a to nejde.
Nahrani a dotaz jsou dva kroky. Jednoho souboru se casto pta vic dotazu a nahravat ho pokazde znovu by stalo cas i penize.
temperature se posila jen kdyz ji uzivatel vyplni. Novejsi modely ji
odmitaji uplne, takze poslat vychozi hodnotu by krok rozbilo tam, kde o ni
nikdo nestal.
E-mail: sluzba, ktera nejde pres HTTP
Zbytek katalogu se vola pres HTTP a operaci vykona skript. SMTP neni HTTP,
a skript umi jen ctx.http - dat mu sit jinudy by zrusilo pravidlo, ze skript
nema jak zavolat ven mimo nas klient.
E-mail je proto vnitrni krok (src/runtime/builtinSteps.ts), stejne jako
zalozeni ticketu. Rozdil je jen v tom, kam saha: ticket do naseho uloziste,
e-mail na posmovni server firmy.
Sluzba to o sobe rika sama, priznakem transport: 'smtp'. Podle nej se rozhodne
i overeni konektoru. Neni to vlastnost konektoru: jak se sluzba vola, je
vlastnost sluzby.
Co si firma vyplni
| Pole | Klic | Poznamka |
|---|---|---|
| SMTP server | host |
napriklad smtp.seznam.cz |
| Port | port |
587 pro STARTTLS, 465 pro sifrovane od zacatku |
| Sifrovani | security |
prazdne se ridi portem, prepsat lze ssl, starttls, zadne |
| Uzivatel | user |
obvykle cela adresa |
| Heslo | password |
tajne, z API se nikdy nevraci |
| Adresa odesilatele | from |
server ji musi povolit |
| Jmeno odesilatele | fromName |
co uvidi prijemce misto hole adresy |
| Adresa pro odpovedi | replyTo |
kdyz maji odpovedi chodit jinam |
Prazdne sifrovani se ridi portem, protoze to je zvyklost, kterou zna kazdy. Vyslovna hodnota to prebije - jsou servery, ktere to maji jinak.
Adresa serveru se hlida stejne jako u HTTP: nesmi mirit do vnitrni site. Vyplnuje ji firma, takze je to jedina zabrana proti tomu, aby si nechala navazat spojeni dovnitr.
Co se vyplnuje v kroku
| Pole | Druh | Poznamka |
|---|---|---|
| Prijemce | text | adresy oddelene carkou |
| Kopie, skryta kopie | text | nepovinne |
| Predmet | text | sablona, tedy Ticket {{ticketId}} |
| Telo zpravy | html | pise se jako HTML, vice radku |
| Textova verze | longtext | bez vyplneni se vyrobi z HTML |
| Adresa pro odpovedi | text | prebije hodnotu z konektoru |
Krok vraci messageId, accepted a rejected, takze se za nim da vetvit
podminkou na to, jestli server nekoho odmitl.
Textova verze neni pridavek. Klient, ktery HTML nezobrazi, by dostal prazdnou zpravu, a filtry nevyzadane posty berou chybejici textovou cast jako priznak spamu.
HTML telo a dosazovani promennych
Druh pole html je novy vedle text a longtext a znamena dve veci: builder
ho vykresli jako vysoke pole s neproporcionalnim pismem, a runtime v nem
escapuje dosazene hodnoty.
Escapuje se hodnota, ne sablona. Znacky, ktere napsal autor sablony, jsou zamer;
ostre zavorky v hodnote od zakaznika ne. Bez toho by text ticketu s <b>
prepsal rozvrzeni zpravy a <script> by se dostal prijemci do schranky.
Deje se to v fillTemplates v executoru, protoze jen tam je jeste videt, co
byla sablona a co dosazena hodnota. Escapovat az vysledek nejde - v nem uz se
to nerozlisi.
Druh pole se urcuje vyslovne v katalogu, ne odhadem podle nazvu. Hadat
podle id === 'html' by fungovalo do prvni akce, ktera to pole pojmenuje jinak.
Overeni konektoru
Misto cteciho volani se konektor prihlasi na server (verify v nodemaileru).
Nic se neodesila, takze test nikomu nic nedorucí, a bez platneho hesla neprojde.
Hlaska rozlisuje, co se stalo, stejne jako u HTTP:
| Co server rekl | Co to znamena |
|---|---|
EAUTH, kod 535 |
udaje dostal a neuznal je, jde o uzivatele a heslo |
EENVELOPE |
neprijal odesilatele nebo prijemce |
ECONNECTION, ETIMEDOUT |
nespojilo se, tedy adresa, port nebo sifrovani |
| kod 4xx | docasne odmitnuti, opakovani ma smysl |
Odeslani e-mailu je jediny vnitrni krok, ktery smi rict "zkus to znovu". Ostatni selhavaji na spatnem nastaveni, ktere se opakovanim nespravi. Nedostupny posmovni server ale za minutu bezet muze, kdezto spatne heslo bude spatne porad.
U schranek s dvoufazovym overenim musi byt v konektoru heslo pro aplikaci, ne heslo k uctu. Server na to odpovi obycejnym 535 a hlaska to proto rika sama.
Soubory ve skriptech
Soubor prochazi krokem stromu jako Base64 retezec. Duvod: parametr skriptu je vzdy hodnota, kterou jde zapsat do JSONu, protoze se uklada do zaznamu behu. Binarni data by se tam nevesla.
Odesila se pres ctx.http.postForm, ktery slozi multipart/form-data:
await ctx.http.postForm('/files', {
purpose: 'user_data',
file: { filename: 'faktura.pdf', base64: inputs.obsah, contentType: 'application/pdf' },
});
Hranici (boundary) dopisuje az fetch. Kdyby si ji skript nastavoval sam,
chybela by v hlavicce a sluzba by telo neprecetla.
Strop je SCRIPT_MAX_UPLOAD_BYTES, vychozi 10 MB. Je zamerne nizsi nez u cizich
sluzeb: OpenAI zvladne stovky megabajtu, nas zaznam behu ne.
Skripty, ktere k realnym sluzbam existuji
| Skript | Co dela |
|---|---|
raynet.find-company |
dohleda firmu, nenalezeno neni chyba |
raynet.upsert-contact |
najde podle e-mailu a doplni, jinak zalozi |
raynet.create-lead |
zalozi poptavku, volitelne s vazbou na firmu |
csob.list-transactions |
prelozi cislo uctu na ID banky a stahne pohyby |
sap-bo.find-business-partner |
hleda pres OData filtr, apostrof se zdvojuje |
sap-bo.list-orders |
objednavky partnera nebo za obdobi |
ppl.create-shipment |
zalozi zasilku a pocka na zpracovani davky |
ppl.track |
stav zasilky, nenalezeno neni chyba |
microsoft365.send-mail |
e-mail z konkretni schranky |
microsoft365.create-event |
schuzka vcetne casove zony |
google.send-email |
Gmail: sklada cely RFC 2822 e-mail |
google.append-sheet-row |
pripise radek do tabulky, ID vytahne z odkazu |
ga4.run-report |
srovna GA4 hlavicky a hodnoty na radky |
search-console.run-report |
pojmenuje dimenze zpatky, CTR na procenta |
google-ads.campaign-report |
GAQL, mikrojednotky na koruny |
sklik.campaign-report |
RPC pole argumentu, halere na koruny |
meta-ads.insights |
vykon reklam, cisla z textu na cisla |
transcription.transcribe |
dva enginy a sloucení, s nahradnikem pri vypadku |
openai.chat |
dotaz na model |
openai.upload-file |
nahrani souboru pres multipart |
openai.ask-about-file |
otazka nad souborem pres Responses API |
openai.transcribe-audio |
prepis zvuku |
openai.list-models |
co ucet umi |
ai-text.classify |
zarazeni s jistotou, hlida vycet kategorii |
ai-text.extract |
vystup podle poli, ktera popsal uzivatel |
ai-text.generate |
text s omezenim delky, tonu a jazyka |
E-mail v tabulce neni schvalne: neni to skript, ale vnitrni krok, viz vyse.
Skripty pro iDoklad jsou popsane v 11-skripty-konektoru.md.
Co chybi
| Chybi | Poznamka |
|---|---|
| Spoustece ze skutecnych sluzeb | triggery v katalogu jsou zatim popis, ne kod |
| Stazeni binarni odpovedi | ctx.http cte odpoved jako text, PDF etikety se nevraci |
| Google: dohledani souboru na Disku | wrapper nema v OpenAPI parametry dotazu, neni jiste, co propousti |
| OAuth toky (Google, CSOB, Meta) | token se zadava rucne a po vyprsení se rucne prepisuje |
| Prilohy u e-mailu | krok posila jen telo, soubor zatim neprilozi |
| Prijem e-mailu (IMAP) | spoustec Prijat e-mail je zatim jen popis v katalogu |
| Overeni udaju u Prepisu hovoru | sluzba nema levne cteci volani, ktere by udaje otestovalo |