דילוג לתוכן
תיעוד Nodaro
תיעודמדריך הרכיביםמודליםתבניותסוכני AI (MCP)מפתחיםאירוח עצמימחקרהשוואות
SDK ל-TypeScript

אפליקציות ותבניות

עיון באפליקציות Nodaro שפורסמו והרצה שלהן מקוד TypeScript, קריאת היסטוריית ההרצות, שכפול תבניות של תהליכים לפרויקט והצגת המדריכים.

client.apps מאפשר לעיין באפליקציות שפורסמו ולהריץ אותן: תהליכים שעטופים בטופס פשוט של קלטים ופלטים. המשאב client.templates מאפשר לעיין בשוק התבניות ולשכפל תבנית לאחד הפרויקטים שלכם, והמשאב client.tutorials מציג את מדריכי הווידאו ומדריכי התהליכים. המושגים מוסברים בדפים אפליקציות ומדריכים ותבניות.

מתודות

מתודהמה היא עושה
apps.list(params?)עיון באפליקציות שפורסמו
apps.get(slug)קריאת אפליקציה עם הקלטים והפלטים שלה
apps.run(slug, inputs?, opts?)הרצת אפליקציה
apps.listRuns(slug, params?)רשימת ההרצות שלכם של אפליקציה
apps.getRun(slug, runId)קריאת הרצה אחת
apps.resume(slug, runId, opts?)ניסיון חוזר של הרצה שנכשלה, מהמקום שבו היא נעצרה
apps.deleteRun(slug, runId)העברת הרצה לארכיון
templates.browse(params?)עיון בשוק התבניות
templates.get(slug)קריאת תבנית עם התהליך שלה
templates.clone(slug, params)העתקת תבנית לפרויקט
tutorials.list()רשימת המדריכים לפי קטגוריה

client.apps

המתודות list() ו-get() הן ציבוריות. המתודה run() ומתודות היסטוריית ההרצות פועלות בשם המשתמש המחובר שקורא להן.

apps.list(params?)‎

מחזירה את האפליקציות שפורסמו, עמוד אחד בכל פעם.

list(params?: { search?: string; category?: string; limit?: number; cursor?: string }): Promise<{
  data: PublishedApp[]
  nextCursor?: string | null
}>

מאפיין

סוג

const { data: apps, nextCursor } = await client.apps.list({ search: "headshot", limit: 20 })

השדות של PublishedApp הם id, slug, name, description, creatorId, creatorName, thumbnailUrl, category, isFeatured, runCount, estimatedCredits, perMinuteCredits, perItemCredits, createdAt ו-updatedAt.

השדה estimatedCredits הוא המחיר הרשום של האפליקציה להרצה אחת. המחיר של חלק מהאפליקציות נקבע לפי האורך של ההקלטה שנותנים להן, כמו מעבר של רכיב תוכנית עריכה או רינדור של פרק שלם. באפליקציות כאלה, הערך של perMinuteCredits גדול מ-0, והמחיר הוא estimatedCredits ועוד perMinuteCredits לכל דקה של ההקלטה. כשלאפליקציה יש קלט רשימה שהמשתמש שלה ממלא, הערך של perItemCredits גדול מ-0: כל פריט מעבר לפריטים שהיוצר שמר מוסיף את מספר הקרדיטים הזה.

המחיר הרשום הוא טווח כשבחירה שעומדת לרשות המשתמש של האפליקציה עולה יותר. השדה maxEstimatedCredits, יחד עם maxPerMinuteCredits ו-maxPerItemCredits, מציין את הסכום הגבוה ביותר שהרצה יכולה לעלות בכל השדות שהאפליקציה חושפת, בתוך המגבלות שהיוצר הגדיר. השדה estimatedCredits והחלקים שלו הם המחיר בהגדרות השמורות, מחיר ה״החל מ״. שדות ה-max* מופיעים רק כשהם שונים. סרטון UGC מתומחר מחדש לכל צירוף, ולכן אורך תסריט או שפה שהמשתמש של האפליקציה יכול לבחור נספרים לפי המחיר הגבוה ביותר שלהם. לאפליקציה שפורסמה לפני שהטווח נוסף אין טווח עד הפרסום הבא שלה.

הערך estimatedCreditsIsEstimate: true מסמן מחיר רשום שכולל אומדן של קריאה ל-LLM שמחויבת לפי שימוש. השדה מופיע באפליקציות, בתבניות ובמדריכים. הקריאה ל-LLM מחויבת לפי השימוש בפועל בטוקנים, ולכן הציגו את המחיר כ-≈N. במחיר קבוע השדה לא מופיע. פרסום של תהליך שמציין מודל LLM שעדיין אין לו מחיר נדחה עם 400 validation_error ועם reason: "llm_model_unpriced". ראו בדף ה-API מודל שעדיין אין לו מחיר. כשאי אפשר לתמחר את סרטון ה-UGC של אפליקציה שמפרסמים, בהגדרות השמורות או בבחירה שעומדת לרשות המשתמש של האפליקציה, האפליקציה אף פעם לא נרשמת כחינמית. הפרסום נדחה עם 503, כשגם הקוד וגם ה-reason הם ugc_listing_unpriced, ועם choice שמציין מה נכשל, למשל "a choice its users can make (Hebrew, 60 s)" (בחירה שהמשתמשים שלה יכולים לעשות: עברית, 60 שניות). שום דבר לא נכתב, ולכן פרסמו שוב. דרך ה-SDK, זו שגיאת NodaroError עם ה-code וה-status האלה.

apps.get(slug)‎

מחזירה אפליקציה אחת, עם inputSchema (השדות שמשתמשי הקצה ממלאים) ועם outputs (מה שהאפליקציה מחזירה).

get(slug: string): Promise<{ data: PublishedAppDetail }>

מאפיין

סוג

const { data: app } = await client.apps.get("pro-headshot")
console.log(app.inputSchema, app.outputs) // outputs: [{ nodeId, label, type }]

השדות runEstimatedCredits, runPerMinuteCredits ו-runPerItemCredits הם המחיר של הרצת האפליקציה לבדה: המחיר הרשום בלי החלק של רינדור סופי, שרץ ומשלמים עליו בנפרד. השדה editPlanPerMinute הוא true כשעל רכיב תוכנית עריכה משלמים לכל דקה של ההקלטה, או חלק ממנה, ו-false כשמשלמים עליו במדרגות של 15, 30, 60, 90, 120 או 180 דקות.

apps.run(slug, inputs?, opts?)‎

מריצה אפליקציה ומחזירה מיד { executionId, status, runId? }. כדי לקבל את התוצאה, קראו ל-client.executions.get(executionId) מדי כמה שניות.

run(slug: string, inputs?: Record<string, unknown>, opts?: {
  inputOverrides?: Record<string, Record<string, unknown>>
}): Promise<{ executionId: string; status: "pending" | "running"; runId?: string }>

מאפיין

סוג

const { executionId } = await client.apps.run("pro-headshot", { photo: photoUrl })

// Add hidden text before the app's prompt, for this run only
await client.apps.run(
  "pro-headshot",
  { photo: photoUrl },
  { inputOverrides: { n1: { promptPrefix: "Studio portrait of" } } },
)

כשאפליקציה מקבלת קישור לסרטון דרך רכיב כתובת וידאו שהיא חושפת, מריצים אותה עם הקישור כערך הקלט, למשל client.apps.run("clip-pack", { episode: "https://www.youtube.com/watch?v=..." }). השרת מוריד את הסרטון מקישור לפוסט לפני שהרכיב הראשון רץ. סרטון יוטיוב של 4 דקות או יותר יורד רק כקטע שאתם מציינים ב-inputOverrides. כל קישור אינטרנט אחר משמש כמו שהוא.

האפשרות inputOverrides מיועדת לשימוש מתקדם. היא יכולה לקבוע, למשל, את הטקסט לפני ואחרי של רכיב (promptPrefix ו-promptSuffix). היא לא יכולה לקבוע יעד של רכיב פלט, כמו הכתובת של רכיב פלט Webhook, חשבון לפרסום או היעד של רכיב גריפה. דריסה כזו נדחית עם 400 locked_field.

apps.listRuns(slug, params?)‎

מחזירה את ההרצות שלכם של אפליקציה, עמוד אחד בכל פעם.

listRuns(slug: string, params?: { limit?: number; cursor?: string }): Promise<{ data: AppRun[]; nextCursor?: string | null }>

מאפיין

סוג

const { data: runs } = await client.apps.listRuns("pro-headshot", { limit: 10 })
for (const run of runs) console.log(run.status, run.outputs)

השדות של AppRun הם id, appSlug, executionId, status (pending, running, completed, failed או cancelled), inputs, outputs (כל פלט הוא { nodeId, type, url?, text? }), startedAt ו-finishedAt.

apps.getRun(slug, runId)‎

מחזירה הרצה אחת של אפליקציה.

getRun(slug: string, runId: string): Promise<{ data: AppRun }>

מאפיין

סוג

const { data: run } = await client.apps.getRun("pro-headshot", runId)

apps.resume(slug, runId, opts?)‎

מנסה שוב הרצה שנכשלה, מהמקום שבו היא נעצרה. זה הכפתור נסו שוב בדף של האפליקציה. רק השלבים שההרצה לא השלימה רצים, ורק עליהם משלמים. כל שלב שההרצה השלימה מעביר הלאה את התוצאה שלו. רכיב קליפ UGC שנכשל מרנדר רק את הקליפים שהוא לא סיים.

resume(slug: string, runId: string, opts: { dryRun: true; version?: number }): Promise<{ data: AppRunResumePlan }>
resume(slug: string, runId: string, opts?: { dryRun?: false; version?: number }): Promise<AppRunResumeResult>

מאפיין

סוג

  • הגרסה והקלטים של ההרצה עצמה. הניסיון החוזר מריץ את גרסת האפליקציה שההרצה הריצה, עם הקלטים שההרצה השתמשה בהם. הוא לא מקבל קלטים משלו.
  • ההרצה היבשה. עם dryRun: true, התשובה היא AppRunResumePlan: runId, continuesExecutionId, appVersion, nodeIds, steps ({ nodeId, type, label }, השלבים שרצים שוב), estimatedCredits, reusedClips, sufficient ו-available. השדה estimatedCredits כולל את תוספת המחיר של היוצר על האפליקציה, והוא null כשאין קרדיטים. השדה sufficient מציין אם היתרה שלכם והקרדיטים לאפליקציות שלכם מכסים את הניסיון החוזר. השדה appCreditsAllowance מופיע רק כשהקרדיטים לאפליקציות הם מה שעוצר אותו. הציגו את התוכנית הזו לפני ניסיון חוזר.
  • ניסיון חוזר שהתחיל. בלי dryRun, התשובה היא { executionId, runId, status: "pending", nodeIds, deduped? }. בדקו את הסטטוס שלו מדי כמה שניות עם client.executions.get(executionId). ההרצה שמאחורי הרצת האפליקציה היא מעכשיו זו של הניסיון החוזר, ולכן המתודה getRun מציגה אותה.
  • ניסיון חוזר אחד בכל פעם. בקריאה שנייה, בזמן שהניסיון החוזר עדיין ממתין, המתודה זורקת AlreadyRunningError שמציינת את ה-executionId שלו. רק שתי לחיצות באותו רגע בדיוק מגיעות לאותו ניסיון חוזר, והתשובה להן כוללת deduped: true.

הדחיות מגיעות לפני שמשהו נכתב ולפני שנגבים קרדיטים. השדה code הוא אחד מהערכים של AppRunResumeRefusalCode:

codeהשגיאה שנזרקתמתי
not_foundNotFoundErrorאין לכם הרצה כזו באפליקציה הזו.
continuation_not_foundNotFoundError (השדה code שלה הוא not_found)ההרצה שמאחורי הרצת האפליקציה לא שייכת לכם.
validation_errorNodaroError (400)מזהה הרצה או גוף בקשה פגומים, או הרצה של רכיב שמור.
app_run_version_unavailableNodaroError (409)הגרסה שההרצה הריצה כבר לא קיימת. התחילו את ההרצה מחדש.
app_run_version_changedAppRunVersionChangedErrorהאפליקציה בגרסה אחרת מזו שההרצה הריצה. התחילו מחדש, או שלחו version: err.runVersion.
continuation_workflow_mismatch / continuation_version_mismatchNodaroError (400)ההרצה שמאחורי הרצת האפליקציה הריצה תהליך אחר, או גרסה אחרת שלו. התחילו את ההרצה מחדש.
continuation_not_failedNodaroError (409)ההרצה הושלמה או נעצרה. התחילו אותה מחדש.
already_runningAlreadyRunningError (409, executionId)ההרצה עדיין רצה, או שניסיון חוזר שלה ממתין. בדקו את המצב של executionId.
continuation_subset_requiredNodaroError (409, או 400)לא נשאר מה לנסות שוב (409), או שהאפליקציה לא מריצה אף שלב (400).
locked_fieldNodaroError (400)קלט שההרצה השתמשה בו קובע שדה שהאפליקציה לא מאפשרת למי שמריץ אותה לקבוע.
preview_review_requiredNodaroError (400)הניסיון החוזר היה נעצר בתצוגה מקדימה, ואין מי שיבדוק אותה. נסו שוב מהדף של האפליקציה.
insufficient_app_credits / insufficient_creditsInsufficientCreditsErrorהקרדיטים לאפליקציות או היתרה שלכם לא מספיקים לניסיון החוזר. השדה code של השגיאה מציין איזה מהשניים.
billing_unavailableNodaroError (503)החיוב לא זמין לרגע בהרצה בסביבת עבודה. נסו שוב בעוד רגע.

כדי להריץ עם קלטים אחרים או בגרסה הנוכחית של האפליקציה, התחילו את ההרצה מחדש עם apps.run. אז כל השלבים רצים שוב, ומשלמים על כולם שוב. ראו את הסעיף ניסיון חוזר של הרצה שנכשלה בדף הטמעת מיני־אפליקציה.

import { AppRunVersionChangedError } from "@nodaro/sdk/errors"

const { data: plan } = await client.apps.resume("pro-headshot", runId, { dryRun: true })
console.log(`${plan.steps.length} step(s) again, about ${plan.estimatedCredits} credits`)

try {
  const { executionId } = await client.apps.resume("pro-headshot", runId)
} catch (err) {
  if (err instanceof AppRunVersionChangedError && err.runVersion !== undefined) {
    // Continue on the run's own version anyway, or start over with apps.run.
    await client.apps.resume("pro-headshot", runId, { version: err.runVersion })
  }
}

apps.deleteRun(slug, runId)‎

מעבירה הרצה לארכיון. שחזור של הרצה ומחיקה לצמיתות שלה זמינים רק באפליקציה של Nodaro, כך שסקריפט לא יכול להשמיד נתונים.

deleteRun(slug: string, runId: string): Promise<{ success: true; archived: true }>

מאפיין

סוג

await client.apps.deleteRun("pro-headshot", runId)

client.templates

שוק התבניות, שהוא ציבורי בכוונה: אפשר לעיין בו, לקרוא תבנית עם כל התהליך שלה ולשכפל אותה לפרויקט שלכם בחינם.

templates.browse(params?)‎

מחזירה תבניות מהשוק, עמוד אחד בכל פעם (GET /v1/templates/browse). אין צורך להתחבר.

browse(params?: BrowseTemplatesParams): Promise<{ data: TemplateBrowseCard[]; nextCursor: string | null }>

מאפיין

סוג

const page = await client.templates.browse({ sort: "popular", search: "trailer" })

השדה estimatedCredits של כרטיס הוא המחיר הרשום של התבנית. כשהמחיר נקבע לפי האורך של ההקלטה שנותנים לתבנית, הערך של estimatedPerMinuteCredits גדול מ-0. המחיר הוא אז estimatedCredits ועוד מספר הקרדיטים הזה לכל דקה של ההקלטה. אחרת, השדה estimatedPerMinuteCredits לא מופיע או שהוא 0. כשבחירה בתבנית יכולה לעלות יותר, הכרטיס כולל גם את maxEstimatedCredits ואת maxEstimatedPerMinuteCredits, הקצה העליון של טווח המחירים.

הנתיב מקבל גם sort=cheapest, סדר המיון הכי פחות קרדיטים של דף התבניות. המיון הוא לפי המחיר של הקלטה של 60 דקות, מהזול ליקר: הערך של estimatedCredits ועוד 60 כפול estimatedPerMinuteCredits. הטיפוס TemplateSort של ה-SDK לא כולל את cheapest, ולכן שלחו אותו עם client.request().

templates.get(slug)‎

מחזירה תבנית ציבורית אחת עם תמונת המצב המלאה של התהליך שלה: snapshotNodes, snapshotEdges ו-snapshotSettings (GET /v1/templates/:slug).

get(slug: string): Promise<Template>

מאפיין

סוג

const template = await client.templates.get("noir-trailer")
console.log(template.snapshotNodes.length)

כשהמזהה בכתובת לא מוכר, כשהתבנית לא רשומה בשוק או כשהיא לא פעילה, המתודה זורקת NotFoundError.

templates.clone(slug, params)‎

מעתיקה תבנית לאחד הפרויקטים שלכם כתהליך חדש (POST /v1/templates/:slug/clone). השכפול לא עולה קרדיטים.

clone(slug: string, params: { projectId: string; name?: string }): Promise<{ workflowId: string; projectId: string }>

מאפיין

סוג

const { workflowId } = await client.templates.clone("noir-trailer", { projectId })
await client.workflows.run(workflowId)

client.tutorials

tutorials.list()‎

מחזירה את כל קטגוריות המדריכים, ובכל אחת את מדריכי הווידאו ומדריכי התהליכים שלה (GET /v1/tutorials). המתודה ציבורית ולקריאה בלבד.

list(): Promise<{ categories: TutorialCategory[] }>
const { categories } = await client.tutorials.list()
for (const category of categories) {
  console.log(category.name, category.videos.length, category.flows.length)
}

השדות של כל קטגוריה הם id, name, slug, sortOrder, videos ו-flows. לכל סרטון יש title, videoUrl ו-thumbnailUrl. כל פריט ב-flows הוא תבנית שמסומנת כמדריך: יש בו title, complexity, estimatedCredits, סוגי הרכיבים והמודלים שהוא משתמש בהם, ו-slug שאפשר להעביר ל-templates.get() ול-templates.clone().

שאלות נפוצות

איך מריצים אפליקציית Nodaro מקוד?

כדי להריץ אפליקציה מקוד, קראו ל-client.apps.run עם המזהה בכתובת של האפליקציה ועם אובייקט inputs שהמפתחות שלו תואמים לשדות הקלט של האפליקציה. המתודה מחזירה executionId. כדי לקבל את התוצאה, קראו ל-client.executions.get(executionId)‎ מדי כמה שניות.

איך מגלים אילו קלטים אפליקציה מצפה לקבל?

כדי לגלות את הקלטים של אפליקציה, קראו ל-client.apps.get(slug)‎. ב-inputSchema של האפליקציה מפורטים השדות שמשתמשי הקצה ממלאים, וב-outputs מפורט מה האפליקציה מחזירה.

האם שכפול תבנית עולה קרדיטים?

לא. שכפול תבנית עם client.templates.clone מעתיק את התבנית לאחד הפרויקטים שלכם בחינם. משלמים רק כשמריצים את התהליך החדש.

איך מנסים שוב מקוד הרצת אפליקציה שנכשלה?

כדי לנסות שוב הרצת אפליקציה שנכשלה, קראו ל-client.apps.resume(slug, runId, { dryRun: true })‎ כדי לראות את השלבים ואת המחיר, ואז קראו למתודה שוב בלי dryRun. רק השלבים שההרצה לא השלימה רצים, ורק עליהם משלמים.

האם inputOverrides יכול לשנות לאן אפליקציה שולחת את התוצאות שלה?

לא. הפרמטר inputOverrides לא יכול לקבוע יעד, כמו הכתובת של רכיב פלט Webhook או חשבון לפרסום. הרצה שמנסה לעשות זאת נדחית עם 400 locked_field.

עודכן לאחרונה

בדף הזה